#include "FileAccess.h"
#include "../String/String.h"

#include <direct.h>

FileAccess::FileAccess() :
	m_isDirectWrite(false),
	m_binaryFile(0),
	m_asciiFile(0)
{
}

FileAccess::~FileAccess()
{
	// If the strings exist then delete them!
	if( m_binaryFile ) delete [] m_binaryFile;
	if( m_asciiFile  ) delete [] m_asciiFile;

	Close();
}

void FileAccess::demandExist( const char* filename )
{
	std::fstream checkExist;
	checkExist.open(filename);
	if( !checkExist.is_open() )
	{
		std::ofstream fileCreate(filename);
		fileCreate.put(0);
		fileCreate.close();
	}
}


// Private function to find the file extention for the
//  ascii file from the binary version, allocate and set the
//  string in the class.
void FileAccess::getAsciiFile( const char* filename )
{
	// Copy the string to custom string class to quickly
	//  parse the file extention from the string.
	String parser = filename;
	int periodTokenCount = parser.getTokenCount(".");					// Find the number of tokens with delimiter "."
	String fileExtention = parser.getToken(periodTokenCount - 1, ".");	// Take the last token with delimiter "."
	int extentionLength	= fileExtention.length();						// Calculate the length of the file extention
	
	// Take the sub string from the beginning of the filename just up
	//  to the last period in the string. This is our base filename
	String baseFile = parser.substr(0, parser.length() - (extentionLength + 1));

	// Add the ascii file extention to the name
	baseFile += ".txt";

	// Set the new ascii filename
	setAscii( baseFile.cstr(), baseFile.length() );
}

void FileAccess::setAscii( const char* str, int len )
{
	// If we're not given a length then calculate it
	if( !len ) len = (int)strlen(str);

	// Check to see if the ascii string already exists, if so then delete it
	if( m_asciiFile ) delete [] m_asciiFile;
	
	// Allocate the memory for the new ascii filename
	m_asciiFile = new char[ len + 1 ];

	// Copy the bytes
	memcpy( m_asciiFile, str, len + 1 );
}

void FileAccess::setBin( const char* str, int len )
{
	// If we're not given a length then calculate it
	if( !len ) len = (int)strlen(str);

	// Check to see if the ascii string already exists, if so then delete it
	if( m_binaryFile ) delete [] m_binaryFile;
	
	// Allocate the memory for the new ascii filename
	m_binaryFile = new char[ len + 1 ];

	// Copy the bytes
	memcpy( m_binaryFile, str, len + 1 );

}

void FileAccess::Close()
{
	// Close all file streams
	m_asciiFileStream.close();
	m_binaryFileStream.close();

	// Make sure we know we don't have any file pointer open.
	m_isOpen = false;
}

bool FileAccess::OpenFile( const char* filename, bool directWrite, bool binary, bool ascii  )
{
	m_readBinary = true;

	// You need to open at least one file
	if( !m_openAscii && !m_openBinary )
		return false;

	// Close any file this object is currently accessing
	Close();

	// Set if we should read/write directly to the file or not
	m_isDirectWrite	= directWrite;

	// Set if we are to open to read/write the binary and ascii files
	m_openBinary	= binary;
	m_openAscii		= ascii;
	
	// Make sure that the binary and ascii filenames are set correctly
	setBin(filename);
	getAsciiFile(filename);

	// Demand the existance of any files we're going to use
	if( m_openBinary ) demandExist(m_binaryFile);
	if( m_openAscii  ) demandExist(m_asciiFile);

	// If the file is direct read/write then we should open the file
	//  stream for reading & writing
	if( m_isDirectWrite )
	{
		bool success = true;
		if( m_openBinary )
		{
			m_binaryFileStream.clear();
			m_binaryFileStream.open(m_binaryFile, std::ios_base::in | std::ios_base::out | std::ios_base::binary);

			// Only successfull if we were actually able to open
			//  the binary file when we wanted to.
			success &= m_binaryFileStream.is_open();	//  should open correctly
		}
		if( m_openAscii  )
		{
			m_asciiFileStream.clear();
			m_asciiFileStream.open(m_asciiFile, std::ios_base::in | std::ios_base::out);

			// Only successfull if we were actually able to open
			//  the ascii file when we wanted to.
			success &= m_asciiFileStream.is_open();
		}
		
		// If we did not have success opening the files, then close both
		if( !success ) Close();

		// Make sure we know the file is open
		m_isOpen = true;

		return success;
	}
	else
	{
	}

	return false;
}

void FileAccess::LoadFromFile(bool binary)
{
	if( !m_isDirectWrite )
	{
		
	}
}

void FileAccess::SaveToFile()
{
	if( !m_isDirectWrite )
	{

	}
}


// Returns the length in bytes of all of the currently stored values
unsigned int FileAccess::GetBinarySize()
{
	return 0;
}

void readAsciiValue( std::fstream& stream, String& value )
{
	char buffer[1024];
	value.clear();

	// Load the entire line into the value
	bool done = false;
	while( !done )
	{
		// Unless this is an insanely long byte array
		// then it should read the line in one read
		stream.getline(buffer, 1024);
		if( stream.gcount() != 1024 )
			done = true;

		// Add the line to the value
		value += buffer;
	}

	if( value.getToken(1,"<>") == "hex" )
	{
		int start	= value.getToken(0,"<>").length() + 7;
		int end		= value.length() - 6;
		value = value.substr(start, end);

		// Since we're reading in a hex value, we need to clear off
		//  one more line from the stream.
		stream.getline(buffer, 1024);
	}
	else
	{
		int start	= value.getToken(0,"<>").length() + 2;	// <length>| = length + 2
		int end		= value.length() - start - 1;			// |</length> = total - start - 1
		value = value.substr(start, end);
	}


}

void readBinary( std::fstream& stream, void* data, unsigned int length )
{
	// Write the binary data directly to the stream
	bool open = stream.is_open();
	bool good = stream.good();
	if( open && good )
	{
		stream.read(static_cast<char*>(data), length);
	}
	else
		assert(0);
}
void writeBinary( std::fstream& stream, const void* data, unsigned int length)
{
	// Write the binary data directly to the stream
	bool open = stream.is_open();
	bool good = stream.good();
	if( open && good )
	{
		stream.write(static_cast<const char*>(data), length);
	}
}

void writeAsciiBegin( std::fstream& stream, const String& name )
{
	bool open = stream.is_open();
	bool good = stream.good();
	if( open && good )
	{
		String value = String("<") + name + String(">") ;
		stream << value.cstr();
	}
}

void writeAsciiValue( std::fstream& stream, const String& value )
{
	bool open = stream.is_open();
	bool good = stream.good();
	if( open && good )
	{
		stream << value.cstr();
	}
}

void writeAsciiEnd( std::fstream& stream, const String& name )
{
	bool open = stream.is_open();
	bool good = stream.good();
	if( open && good )
	{
		String value = String("</") + name + String(">");
		stream << value.cstr() << std::endl;
	}
}

void writeAscii( std::fstream& stream, const String& name, const String& value )
{
	writeAsciiBegin(	stream,	name	);
	writeAsciiValue(	stream,	value	);
	writeAsciiEnd(		stream,	name	);
}


void FileAccess::WriteByte(unsigned char value, const char* name)
{
	if( m_isDirectWrite )
	{
		// If the binary file is open to write, then output the
		//  value to the binary file
		if( m_openBinary )
			writeBinary( m_binaryFileStream, &value, sizeof(unsigned char) );

		// If the ascii file is open to write, then output the 
		//  value to the ascii file
		if( m_openAscii )
		{
			if( name )
			{
				writeAscii( m_asciiFileStream, name, value );
			}
			else
			{
				writeAscii( m_asciiFileStream, "unsigned char", value );
			}
		}
	}
}

void FileAccess::WriteBytes(const void* bytes, unsigned int length, const char* name)
{
	if( m_isDirectWrite )
	{
		// If the binary file is open to write, then output the
		//  value to the binary file
		if( m_openBinary )
			writeBinary( m_binaryFileStream, bytes, length );

		// If the ascii file is open to write, then output the 
		//  value to the ascii file
		if( m_openAscii )
		{
			if( name )
				writeAsciiBegin( m_asciiFileStream, name );
			else
				writeAsciiBegin( m_asciiFileStream, "bytes" );

			writeAsciiBegin( m_asciiFileStream, "hex" );

			const char*		hex[] = {"0","1","2","3","4","5","6","7","8","9","A","B","C","D","E","F"};
			for( unsigned int i=0; i<length; ++i )
			{
				unsigned char byte = static_cast<const unsigned char*>(bytes)[i];
				String hexval = String( hex[ (byte & 0xF0) >> 4 ] ) + String( hex[ byte & 0x0F ] ) + ((i < length-1)? String(",") : String(""));
				writeAsciiValue( m_asciiFileStream, hexval );

			}
			writeAsciiEnd( m_asciiFileStream, "hex" );

			if( name )
				writeAsciiEnd( m_asciiFileStream, name );
			else
				writeAsciiEnd( m_asciiFileStream, "bytes" );
		}
	}
}

void FileAccess::WriteChar(char value, const char* name)
{
	if( m_isDirectWrite )
	{
		// If the binary file is open to write, then output the
		//  value to the binary file
		if( m_openBinary )
			writeBinary( m_binaryFileStream, &value, sizeof(char) );

		// If the ascii file is open to write, then output the 
		//  value to the ascii file
		if( m_openAscii )
		{
			if( name )
				writeAscii( m_asciiFileStream, name, value );
			else
				writeAscii( m_asciiFileStream, "char", value );
		}
	}
}
void FileAccess::WriteShort(short value, const char* name)
{
	if( m_isDirectWrite )
	{
		// If the binary file is open to write, then output the
		//  value to the binary file
		if( m_openBinary )
			writeBinary( m_binaryFileStream, &value, sizeof(short) );

		// If the ascii file is open to write, then output the 
		//  value to the ascii file
		if( m_openAscii )
		{
			if( name )
				writeAscii( m_asciiFileStream, name, value );
			else
				writeAscii( m_asciiFileStream, "short", value );
		}
	}
}
void FileAccess::WriteUnsignedShort(unsigned short value, const char* name)
{
	if( m_isDirectWrite )
	{
		// If the binary file is open to write, then output the
		//  value to the binary file
		if( m_openBinary )
			writeBinary( m_binaryFileStream, &value, sizeof(unsigned short) );

		// If the ascii file is open to write, then output the 
		//  value to the ascii file
		if( m_openAscii )
		{
			if( name )
				writeAscii( m_asciiFileStream, name, value );
			else
				writeAscii( m_asciiFileStream, "unsigned short", value );
		}
	}
}
void FileAccess::WriteLong(long value, const char* name)
{
	if( m_isDirectWrite )
	{
		// If the binary file is open to write, then output the
		//  value to the binary file
		if( m_openBinary )
			writeBinary( m_binaryFileStream, &value, sizeof(long) );

		// If the ascii file is open to write, then output the 
		//  value to the ascii file
		if( m_openAscii )
		{
			if( name )
				writeAscii( m_asciiFileStream, name, value );
			else
				writeAscii( m_asciiFileStream, "long", value );
		}
	}
}

void FileAccess::WriteUnsignedLong(unsigned long value, const char* name)
{
		if( m_isDirectWrite )
	{
		// If the binary file is open to write, then output the
		//  value to the binary file
		if( m_openBinary )
			writeBinary( m_binaryFileStream, &value, sizeof(unsigned long) );

		// If the ascii file is open to write, then output the 
		//  value to the ascii file
		if( m_openAscii )
		{
			if( name )
				writeAscii( m_asciiFileStream, name, String(value) );
			else
				writeAscii( m_asciiFileStream, "unsigned long", String(value) );
		}
	}
}

void FileAccess::WriteInteger(int value, const char* name)
{
	if( m_isDirectWrite )
	{
		// If the binary file is open to write, then output the
		//  value to the binary file
		if( m_openBinary )
			writeBinary( m_binaryFileStream, &value, sizeof(int) );

		// If the ascii file is open to write, then output the 
		//  value to the ascii file
		if( m_openAscii )
		{
			if( name )
				writeAscii( m_asciiFileStream, name, value );
			else
				writeAscii( m_asciiFileStream, "int", value );
		}
	}
}

void FileAccess::WriteUnsignedInt(unsigned int value, const char* name)
{
	if( m_isDirectWrite )
	{
		// If the binary file is open to write, then output the
		//  value to the binary file
		if( m_openBinary )
			writeBinary( m_binaryFileStream, &value, sizeof(unsigned int) );

		// If the ascii file is open to write, then output the 
		//  value to the ascii file
		if( m_openAscii )
		{
			if( name )
				writeAscii( m_asciiFileStream, name, String(value) );
			else
				writeAscii( m_asciiFileStream, "unsigned int", String(value) );
		}
	}
}

void FileAccess::WriteFloat(float value, const char* name)
{
	if( m_isDirectWrite )
	{
		// If the binary file is open to write, then output the
		//  value to the binary file
		if( m_openBinary )
			writeBinary( m_binaryFileStream, &value, sizeof(float) );

		// If the ascii file is open to write, then output the 
		//  value to the ascii file
		if( m_openAscii )
		{
			if( name )
				writeAscii( m_asciiFileStream, name, value );
			else
				writeAscii( m_asciiFileStream, "float", value );
		}
	}
}

void FileAccess::WriteDouble(double value, const char* name)
{
	if( m_isDirectWrite )
	{
		// If the binary file is open to write, then output the
		//  value to the binary file
		if( m_openBinary )
			writeBinary( m_binaryFileStream, &value, sizeof(double) );

		// If the ascii file is open to write, then output the 
		//  value to the ascii file
		if( m_openAscii )
		{
			if( name )
				writeAscii( m_asciiFileStream, name, value );
			else
				writeAscii( m_asciiFileStream, "double", value );
		}
	}
}


void FileAccess::ReadBytes(	void* bytes, unsigned int length )
{
	if( m_readBinary )
		readBinary( m_binaryFileStream, bytes, length );
	else
	{
		String value;
		readAsciiValue( m_asciiFileStream, value );
	}
}

unsigned char FileAccess::ReadByte()
{
	unsigned char result = 0;
	if( m_readBinary )
	{
		readBinary( m_binaryFileStream, &result, sizeof(result) );
	}
	else
	{
		assert(0);
		//String value;
		//readAsciiValue( m_asciiFileStream, value );
	}
	return result;
}
char FileAccess::ReadChar()
{
	char result = 0;
	if( m_readBinary )
		readBinary( m_binaryFileStream, &result, sizeof(result) );
	else
	{
		assert(0);
	}
	return result;
}
short FileAccess::ReadShort()
{
	short result = 0;
	if( m_readBinary )
		readBinary( m_binaryFileStream, &result, sizeof(result) );
	else
	{
		assert(0);
	}
	return result;
}
unsigned short	FileAccess::ReadUnsignedShort()
{
	unsigned short result = 0;
	if( m_readBinary )
		readBinary( m_binaryFileStream, &result, sizeof(result) );
	else
	{
		assert(0);
	}
	return result;
}
long			FileAccess::ReadLong()
{
	long result = 0;
	if( m_readBinary )
		readBinary( m_binaryFileStream, &result, sizeof(result) );
	else
	{
		assert(0);
	}
	return result;
}
unsigned long	FileAccess::ReadUnsignedLong()
{
	unsigned long result = 0;
	if( m_readBinary )
		readBinary( m_binaryFileStream, &result, sizeof(result) );
	else
	{
		assert(0);
	}
	return result;
}
int				FileAccess::ReadInteger()
{
	int result = 0;
	if( m_readBinary )
		readBinary( m_binaryFileStream, &result, sizeof(result) );
	else
	{
		assert(0);
	}
	return result;
}
unsigned int	FileAccess::ReadUnsignedInt()
{
	unsigned int result = 0;
	if( m_readBinary )
		readBinary( m_binaryFileStream, &result, sizeof(result) );
	else
	{
		assert(0);
	}
	return result;
}
float			FileAccess::ReadFloat()
{
	float result = 0;
	if( m_readBinary )
		readBinary( m_binaryFileStream, &result, sizeof(result) );
	else
	{
		assert(0);
	}
	return result;
}
double			FileAccess::ReadDouble()
{
	double result = 0;
	if( m_readBinary )
		readBinary( m_binaryFileStream, &result, sizeof(result) );
	else
	{
		assert(0);
	}
	return result;
}


String			FileAccess::ReadLine()
{
	String result;
	if( m_readBinary )
	{
		char	byte;
		String	buffer;

		bool	done = false;
		while( !done )
		{
			readBinary( m_binaryFileStream, &byte, 1 );
			if( EndOfFile(true) || byte == '\n' )
				done = true;
			else if( byte != 13 )
				result += byte;
		}
	}
	else
	{
		assert(0);
	}
	return result;
}

bool FileAccess::EndOfFile(bool binary)
{
	if( binary )
		return m_binaryFileStream.eof();
	else
		assert(0);	// Not yet implemented

	return false;
}